﻿using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Cqrs.Infrastructure;
using System.Diagnostics;
using System.Linq;
using Cqrs.Infrastructure.Aggregate;
using Cqrs.Common;

namespace Cqrs.StateMachineDesigner.Database
{
    // Copyright © Microsoft Corporation.  All Rights Reserved. 
    // This code released under the terms of the  
    // Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.) 
    // 
    //Copyright (C) Microsoft Corporation.  All rights reserved. 
    public class ObjectDumper
    {
        public static string Write(object o)
        {
            return Write(o, 0);
        }

        public static string Write(object o, int depth)
        {
            ObjectDumper dumper = new ObjectDumper(depth);
            dumper.WriteObject(null, o);
            return dumper._writer;
        }

        string _writer;
        int _pos;
        int _level;
        int _depth;

        private ObjectDumper(int depth)
        {
            this._depth = depth;
        }

        private void Write(string s)
        {
            if (s != null)
            {
                Debug.Write(s);
                _writer += s;
                _pos += s.Length;
            }
        }

        private void WriteIndent()
        {
            for (int i = 0; i < _level; i++) WriteTab(); ;
        }

        private void WriteLine()
        {
            Debug.WriteLine("");
            _writer += "\n";
            _pos = 0;
        }

        private void WriteTab()
        {
            Write("\t");
            //while (pos % 8 != 0) Write(" ");
        }

        private void WriteObject(string prefix, object o)
        {
            if (o == null || o is ValueType || o is string)
            {
                WriteIndent();
                Write(prefix);
                WriteValue(o);
                WriteLine();
            }
            else if (o is IEnumerable)
            {
                foreach (object element in (IEnumerable)o)
                {
                    if (element is IEnumerable && !(element is string))
                    {
                        WriteIndent();
                        Write(prefix);
                        Write("...");
                        WriteLine();
                        if (_level < _depth)
                        {
                            _level++;
                            WriteObject(prefix, element);
                            _level--;
                        }
                    }
                    else
                    {
                        WriteObject(prefix, element);
                    }
                }
            }
            else
            {
                OutputMembers(prefix, o);
            }
        }

        private void OutputMembers(string prefix, object o)
        {
            // todo - awful code!

            bool exit = false;
            Type concreteType = o.GetType();
            Type baseType = concreteType;
            bool concreteLevel = true;

            do
            {
                // todo - comment.
                MemberInfo[] members;
                if (concreteLevel)
                    members = o.GetType().GetMembers(
                        BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                else
                {
                    if (o.GetType().IsSubclassOf(typeof(AggregateRootWithState)))
                    {
                        baseType = baseType.BaseType;
                        members = baseType.GetMembers(BindingFlags.Instance | BindingFlags.NonPublic);
                    }
                    else
                        break;
                }

                WriteIndent();
                Write(prefix);
                if (prefix != null) WriteLine();

                bool propWritten = false;
                foreach (MemberInfo m in members)
                {
                    FieldInfo f = m as FieldInfo;
                    PropertyInfo p = m as PropertyInfo;
                    if (f != null || p != null)
                    {
                        Type t = f != null ? f.FieldType : p.PropertyType;

                        if (!propWritten)
                        {
                            propWritten = true;
                        }

                        // TODO - refactor this bit...
                        if (t.IsValueType || t == typeof(string))
                        {
                            WriteName(m);
                            WriteValue(f != null ? f.GetValue(o) : p.GetValue(o, null));
                            WriteLine();
                        }
                        else if (t == typeof(State))
                        {
                            WriteName(m);
                            if (p != null)
                                Write(((State)p.GetValue(o, null)).Name);
                            else if (f != null)
                                Write(f.Name);
                            WriteLine();
                        }
                        else
                        {
                            if (!typeof(IEnumerable).IsAssignableFrom(t))
                            {
                                WriteName(m);
                                Write("{ }");
                                WriteLine();
                            }
                        }
                    }
                }

                if (concreteLevel)
                {
                    if (_level < _depth)
                    {
                        foreach (MemberInfo m in members)
                        {
                            FieldInfo f = m as FieldInfo;
                            PropertyInfo p = m as PropertyInfo;
                            if (f != null || p != null)
                            {
                                Type t = f != null ? f.FieldType : p.PropertyType;
                                if (!(t.IsValueType || t == typeof(string)))
                                {
                                    object value = f != null ? f.GetValue(o) : p.GetValue(o, null);
                                    if (value != null)
                                    {
                                        _level++;
                                        WriteObject(m.Name + ": ", value);
                                        _level--;
                                    }
                                }
                            }
                        }
                    }
                }
                concreteLevel = false;

                if (baseType.BaseType == null)
                    exit = true;
            } while (exit == false);

        }

        private void WriteName(MemberInfo m)
        {
            WriteIndent();
            Write(m.Name);
            Write("=");
        }

        private void WriteValue(object o)
        {
            if (o == null)
            {
                Write("null");
            }
            else if (o is DateTime)
            {
                Write(((DateTime)o).ToShortDateString() + " " + ((DateTime)o).ToShortTimeString());
            }
            else if (o is ValueType || o is string)
            {
                Write(o.ToString());
            }
            else if (o is IEnumerable)
            {
                Write("...");
            }
            else
            {
                Write("{ }");
            }
        }
    }
}
